#pragma once

#include <unordered_map>

#include "target.h"


namespace evm { namespace messaging {

using namespace std;

struct Filter {
    virtual bool match(const Json &args) = 0;
};

using FilterPtr = shared_ptr<Filter>;

struct Endpoint {
    TargetPtr target;
    FilterPtr filter_rule;

    Endpoint(TargetPtr target = nullptr) : target(target) { }

    /* Remotely callable endpoint method must has the following signatures. */
    using CallMethod = Json (Endpoint::*)(const Json &);
    using CastMethod = void (Endpoint::*)(const Json &);

    bool has(const string &method) {
        return call_methods.count(method) || cast_methods.count(method);
    }

    /* Call endpoint method with specified method name, and args. */
    Json operator()(const string &method, const Json &args) {
        if (call_methods.count(method)) {
            CallMethod m = call_methods[method];
            return (this->*m)(args);
        }

        if (cast_methods.count(method)) {
            CastMethod m = cast_methods[method];
            (this->*m)(args);
            return Json();
        }

        throw invalid_argument("Endpoint method '" + method + "' not found.");
    }

protected:
    /* Register endpoint method with name.
     * Since C++ has no builtin reflection mechanism, dynamic mapping
     * from method name to function pointer must be registered firstly.
     * e.g.,
     * struct C : Endpoint {
     *     C() {
     *         target = make_shared<Target>("topic_test", "namespace_test", "server_test");
     *
     *         reg("create", &C::create);
     *         reg("update", &C::update);
     *     }
     *
     *     Json create(const Json& args) {
     *         return Json();
     *     }
     *
     *     Json update(const Json& args) {
     *         return Json();
     *     }
     * };
     */
    template<typename T, typename R, typename E = typename enable_if<is_same<R, void>::value>::type>
    void reg(string name, R (T::* method)(const Json&)) {
        cast_methods.insert(make_pair(std::move(name), static_cast<CastMethod>(method)));
    }

    template<typename T, typename R, typename enable_if<is_same<R, Json>::value>::type* = nullptr>
    void reg(string name, R (T::* method)(const Json&)) {
        call_methods.insert(make_pair(std::move(name), static_cast<CallMethod>(method)));
    }

    unordered_map<string, CallMethod> call_methods;
    unordered_map<string, CastMethod> cast_methods;
};

using EndpointPtr = shared_ptr<Endpoint>;
using Endpoints = vector<EndpointPtr>;

}}
